#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
#include <gmp.h>
#include <time.h>

mpz_t N_ITER;
unsigned long N_THREADS;
mpz_t pontos_dentro;
mpz_t total_pontos;

pthread_mutex_t mutexPontos;
pthread_mutex_t mutexIter;

void *monte_carlo(void *nada) {
  double x, y;

  do {
    x = (double)(RAND_MAX - rand())/(double)RAND_MAX;
    y = (double)(RAND_MAX - rand())/(double)RAND_MAX;
    
    if(x*x + y*y <= 1) {
      /* Região critica */
      pthread_mutex_lock(&mutexPontos);
      mpz_add_ui(pontos_dentro,pontos_dentro,1);      
      pthread_mutex_unlock(&mutexPontos);
    }

    pthread_mutex_lock(&mutexIter);
    mpz_add_ui(total_pontos,total_pontos,1);
    pthread_mutex_unlock(&mutexIter);
  } while(mpz_cmp(total_pontos,N_ITER) < 0);

  pthread_exit(NULL);  
}

int main(int argc, char *argv[]) {
  pthread_t *threads;
  long i;
  int rc;
  mpf_t pi;
  mpf_t mpf_pontos_dentro;
  mpf_t mpf_total_pontos;
  clock_t begin, end;
  
  FILE *f = fopen("pi_montecarlo_paralelo.txt","w+");

  mpz_init(pontos_dentro);
  mpz_init(total_pontos);
  mpf_init(mpf_pontos_dentro);
  mpf_init(mpf_total_pontos);
  mpf_init2(pi,40000000);
  mpz_init(N_ITER);

  if(argc != 3) {
    printf("Usage: %s [n_threads] [n_interations]\n",argv[0]);
  } else {
    sscanf(argv[1],"%ld",&N_THREADS);
    gmp_sscanf(argv[2],"%Zd",N_ITER);
  }

  pthread_mutex_init(&mutexPontos,NULL);
  pthread_mutex_init(&mutexIter,NULL);

  threads = (pthread_t *)malloc(N_THREADS*sizeof(pthread_t));

  srand(time(NULL));
  begin = clock();
  for(i=0;i < N_THREADS;i++) {
    rc = pthread_create(&threads[i],NULL,monte_carlo,NULL);

    if(rc) {
      printf("Erro na criação da thread.\n");
      exit(-1);
    }
  }

  
  for(i=0;i < N_THREADS;i++) {
    pthread_join(threads[i],NULL);
  }

  mpf_set_z(mpf_pontos_dentro,pontos_dentro);
  mpf_set_z(mpf_total_pontos,total_pontos);

  mpf_div(pi,mpf_pontos_dentro,mpf_total_pontos);
  mpf_mul_ui(pi,pi,4);
  end = clock();

  double time_e = (double)(end - begin)/(double)CLOCKS_PER_SEC;
  printf("Tempo de cálculo: %lf\n",time_e/(double)N_THREADS);

  if(f != NULL) {
    mpf_out_str(f,10,0,pi);
  }

  pthread_mutex_destroy(&mutexPontos);
  pthread_mutex_destroy(&mutexIter);

  mpz_clears(pontos_dentro,total_pontos,N_ITER,NULL);
  mpf_clears(pi,mpf_pontos_dentro,mpf_total_pontos,NULL);

  pthread_exit(NULL);  

  return 0;
}
